package fi.iki.joker.mobilogger.blogs;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import org.apache.xmlrpc.client.XmlRpcCommonsTransportFactory;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import fi.iki.joker.mobilogger.R;
import fi.iki.joker.mobilogger.util.AlertUtil;

/**
 * LiveJournal works with a similar type of XML-RPC interface 
 * as does the metaweblog protocol. Essentially, the createpost
 * message is different. The method is blogger.newPost rather than
 * metaweblog.newpost.
 * @author juha
 *
 */

public class LiveJournalAPI implements
		BlogInterface, OnClickListener,
		AdapterView.OnItemSelectedListener {

	private final String TAG = "LiveJournalAPI";
	private final String LJ_APPKEY = "0123456789ABCDEF";
	private final String MSG_KEY="value";
	private final String STATUS_KEY="status";
	private final String STATUS_RESPONSE_NULL = "1";
	private final String STATUS_ZERO_BLOGS = "2";
	private final String STATUS_OK = "3";
	private final String STATUS_BAD_LOGIN = "4";
	private final String RESPONSE_NAMES_KEY = "response_names";
	private final String RESPONSE_IDS_KEY = "response_ids";
	private final String NO_POST_URL_FOR_ENTRY = "NO_POST_URL_FOR_ENTRY";
	private Activity parentRef = null;
	private ProgressDialog fetchProgress = null;
	private String[] blogNames = null;
	private String[] blogIds = null;
	private String[] listData = null;
	private Spinner userblogs = null;
	private int fetchStatus = 0;
	private int currentySelectedBlog = -1;
	private String configBlogID = null;
	private final int CONFIG_URL_ID = 101;
	
	//the handler for the UI callback from the publish thread
	final Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			Bundle content = msg.getData();
			String progressId = content.getString(MSG_KEY);
			if(progressId != null) {
				if(progressId.equals("1")) {
					fetchProgress.setMessage("Preparing API ...");
					fetchStatus = 1;
				} else if(progressId.equals("2")) {
					fetchProgress.setMessage("Authenticating...");
					fetchStatus = 2;
				} else if(progressId.equals("3")) {
					fetchProgress.setMessage("Contacting server...");
					fetchStatus = 3;
				} else if(progressId.equals("4")) {
					fetchProgress.setMessage("Extracting response...");
					fetchStatus = 4;
				} else if(progressId.equals("5")) {
					fetchProgress.setMessage("Done...");
					fetchStatus = 5;
				}
				String status = content.getString(STATUS_KEY);
				if(status != null && fetchStatus == 5) {
					if(status.equals(STATUS_RESPONSE_NULL)) {
						fetchStatus = 6;
					} else if(status.equals(STATUS_ZERO_BLOGS)) {
						fetchStatus = 7;
					} else if(status.equals(STATUS_OK)) {
						// keep fetch status as 5 to indicate success to showFetchStatus method.
						fetchProgress.setMessage("Displaying blog names...");
						String names = content.getString(RESPONSE_NAMES_KEY);
						Log.d(TAG,"names here: "+names);
						String ids = content.getString(RESPONSE_IDS_KEY);
						Log.d(TAG,"ids here: "+ids);
						String [] namearr = names.split("\\|");
						String [] idsarr = ids.split("\\|");
						for(int i = 0; i < namearr.length; i++) {
							Log.d(TAG,"namearr["+i+"]="+namearr[i]);
						}
						if(namearr == null || namearr.length < 1) {
							fetchStatus = 8;
						} else if (idsarr == null || idsarr.length < 1) {
							fetchStatus = 9;
						} else {
							// extract the response from thread and feed to Spinner.
							blogNames = namearr;
							blogIds = idsarr;
						}
						updateSpinner(userblogs);
					} else if(status.equals(STATUS_BAD_LOGIN)) {
						fetchStatus = 10;
					}
				}
				// Continue from here by handling the responses from the metafeed fetch thread.
			} else {
				// panic!
			}
		}
	};
	
	final Runnable mFetchResults = new Runnable() {
		public void run () {
				showFetchStatus();
		}
	};
	
	public void setInstanceConfig(CharSequence config) {
		Log.d(TAG,"Setting instance config: "+config);
		// sanity checking whether this is an url...
	    this.configBlogID = config.toString();
	}
	
	public String getAuthId(String username, String password) {
		//since livejournal api sends the credentials as part of the
		//createpost call, this just catenates the username and password with
		//|| as field separator. This is then send to the createPost by caller
		//which again parses it.
		String res = username+"||"+password;
		return res;
	}

	public void createConfigEditorRows(Activity parent, TableLayout tl,
			Context context, int startIdFrom, int populateFromId) {
		this.parentRef = parent;
		// first row of the blogger config. Label text and a button to click to start the fetching
		// of blogs from google:
		TableRow tr1 = new TableRow(context);
		// second table row has the label and a spinner which has the fetched blog 
		// entry names the user has.
		TableRow tr2 = new TableRow(context);
    	tr1.setId(startIdFrom);
    	tr2.setId(startIdFrom+1);
		TextView a = new TextView(context);
		Button b = new Button(context);
		userblogs = new Spinner(context);
		b.setText("Fetch");
		b.setOnClickListener(this);
		TableRow.LayoutParams lp = new TableRow.LayoutParams();
		a.setText("Fetch blogs\n(enter credentials\nfirst above)");
		updateSpinner(userblogs);
		tr1.addView(a, lp);
		tr1.addView(b,lp);
		tr2.addView(userblogs,lp);
		tl.addView(tr1, new TableLayout.LayoutParams());
		tl.addView(tr2, new TableLayout.LayoutParams());
	}

	public void onItemSelected(AdapterView parent, View v, int position, long id) {
		this.currentySelectedBlog = position;
		
	}
	public void onNothingSelected(AdapterView arg0) {
		this.currentySelectedBlog = -1;
	}
	public void onClick(View arg0) {
		getUserBlogs();
	}
	
	private void getUserBlogs() {
		if(parentRef == null) {
			Log.e(TAG, "Error: Can't get the parent blogs without a valid reference to invoking activity!");
			throw new IllegalStateException("Cannot continue with null parent!");			
		}
		String username = null;
		String password = null;
		EditText usernameView = (EditText)parentRef.findViewById(R.id.usernameconfig);
		if(usernameView.getText() == null || usernameView.getText().length() < 1 ) {
			AlertUtil.showAlert(parentRef,"Username needed", "You need to give your LiveJournal username in order to continue!");
			return;
		} else {
			username = usernameView.getText().toString();
		}
		usernameView = null;
		EditText passwordView = (EditText)parentRef.findViewById(R.id.passwordconfig);
		if(passwordView.getText() == null || passwordView.getText().length() < 1) {
			AlertUtil.showAlert(parentRef,"Password needed","You need to give your LiveJournal	 password in order to continue!");
			return;
		} else {
			password = passwordView.getText().toString();
		}
		final String fetchUsername = username;
		final String fetchPassword = password;
		fetchProgress = ProgressDialog.show(parentRef, "Fetching your blogs", "Starting to fetch blogs...");
		Thread fetchLJBlogs = new Thread() {
			public void run() {
				Bundle status = new Bundle();
				Message statusMsg = mHandler.obtainMessage();
				status.putString(MSG_KEY, "1");
				statusMsg.setData(status);
				mHandler.sendMessage(statusMsg);
				XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
				String localPostUrl = getPostUrl(null);
				statusMsg = mHandler.obtainMessage();
				status.putString(MSG_KEY, "2");
				statusMsg.setData(status);
				mHandler.sendMessage(statusMsg);
				try {
			    	config.setServerURL(new URL(localPostUrl));
			    } catch (MalformedURLException e) {
			    	Log.e(TAG,"Unable to configure XML-RPC client! Post URL ("+localPostUrl+") is invalid.");
			    }
			    statusMsg = mHandler.obtainMessage();
				status.putString(MSG_KEY, "3");
				statusMsg.setData(status);
				mHandler.sendMessage(statusMsg);
			    XmlRpcClient client = new XmlRpcClient();
				Object [] params = new Object[] {
					LJ_APPKEY,
					fetchUsername,
					fetchPassword
				};
				client.setTransportFactory(new XmlRpcCommonsTransportFactory(client));
				client.setConfig(config);
				Object resobject = null;
				statusMsg = mHandler.obtainMessage();
				status.putString(MSG_KEY, "4");
				statusMsg.setData(status);
				mHandler.sendMessage(statusMsg);
				try {
					resobject = client.execute( "blogger.getUsersBlogs", params );
				} catch (XmlRpcException e) {
					Log.e(TAG,"Exception while executing xml RPC call!");
					Log.d(TAG,"Exception message:"+e.getMessage());
				} catch (ClassCastException ce) {
					Log.e(TAG,"The response object ("+resobject+") doesnt't seem to cast too well.");
					//seems that we get null here if the credentials are wrong, so let's assume that.
					if(resobject == null) {
						statusMsg = mHandler.obtainMessage();
						status.putString(STATUS_KEY, STATUS_BAD_LOGIN);
						status.putString(MSG_KEY, "5");
						statusMsg.setData(status);
						mHandler.sendMessage(statusMsg);
						mHandler.post(mFetchResults);
						return;
					}
				}
				Object[] temp = new Object[1];
				HashMap<String,String> struct = null;
				try {
					temp[0] = ((Object[])resobject)[0];
					struct = (HashMap<String,String>)temp[0];
				} catch (Exception e) {
					Log.e(TAG,"Caught exception when retrieving xml-rpc response: "+e.getMessage());
				}
				//--------------
				if(struct == null) {
					status.putString(STATUS_KEY, STATUS_RESPONSE_NULL);
				} else if( struct.keySet() == null || struct.keySet().size() == 0) {
					status.putString(STATUS_KEY, STATUS_ZERO_BLOGS);
				} else {
					status.putString(STATUS_KEY, STATUS_OK);
				}
				StringBuffer titles = new StringBuffer();
				StringBuffer ids = new StringBuffer();
				Iterator iter = struct.keySet().iterator();
				while(iter.hasNext()) {
					String setKey = (String)iter.next();
					Log.d(TAG,"Key:"+setKey);
					Log.d(TAG,"Value:"+struct.get(setKey));
					if("blogName".equals(setKey)) {
						String title = struct.get("blogName");
						titles.append(title+"|");
						Log.d(TAG,"Found title:"+title);
					} else if("blogid".equals(setKey)) {
						String id = struct.get("blogid");
						ids.append(id+"|");
						Log.d(TAG,"Found id:"+id);
					}
					
				}
				statusMsg = mHandler.obtainMessage();
				status.putString(RESPONSE_NAMES_KEY,titles.toString());
				status.putString(RESPONSE_IDS_KEY, ids.toString());
				//--------------
				statusMsg = mHandler.obtainMessage();
				status.putString(MSG_KEY, "5");
				statusMsg.setData(status);
				mHandler.sendMessage(statusMsg);
				mHandler.post(mFetchResults);
			}
		}; // end Thread
		fetchLJBlogs.start();
		fetchProgress.setMessage("Started to fetch your blogs...");
	}

	public boolean createPost(Activity parent, String authToken,
			String postUrl, String titleType, String title, String contentType,
			String content, String authorName, String authorEmail,
			boolean isDraft) {
		String username = "unassigned";
		String password = "unassigned";
		if(authToken.matches("(.*)\\|\\|(.*)")) {
			String[] splt = authToken.split("\\|\\|");
			username = splt[0];
			Log.d(TAG,"Set username from config: "+username);
			password = splt[1];
			Log.d(TAG,"Set password from config: "+password);
		} else {
			Log.e(TAG,"Cannot parse username and password from the auth token!");
		}
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
	    // check the config var
		String localPostUrl = getPostUrl(authToken);
		try {
	    	config.setServerURL(new URL(localPostUrl));
	    } catch (MalformedURLException e) {
	    	Log.e(TAG,"Unable to configure XML-RPC client! Post URL ("+localPostUrl+") is invalid.");
	    }
	    //The blog id number, as String
	    String blogIdNum = this.configBlogID;
	    Boolean publish = new Boolean(true);
	    if(isDraft) {
	    	publish = new Boolean(false);
	    } else {
	    	publish = new Boolean(true);
	    }
	    //LJ just wants a string body with title on the first row.
	    String postBody = "<title>"+title+"</title>\n"+
	    	content.toString();
		XmlRpcClient client = new XmlRpcClient();
		Object [] params = new Object[] {
			LJ_APPKEY,
			""+blogIdNum,
			username,
			password,
			postBody,
			publish
		};
		client.setTransportFactory(new XmlRpcCommonsTransportFactory(client));
		client.setConfig(config);
		String postID = null;
		try {
			postID = (String) client.execute( "blogger.newPost", params );
		} catch (XmlRpcException e) {
			Log.e(TAG,"Exception while executing xml RPC call!");
			Log.d(TAG,"Exception message:"+e.getMessage());
			
		}
		if((postID == null)||postID.equals("")) { 
			Log.e(TAG,"XML-RPC client returned null or void return code.");
			return false;
		} else if(postID.matches(""+this.configBlogID+"\\:(\\d+)")) {
			Log.d(TAG,"XML-RPC client returned good postID:"+postID);
			return true;
		} else {
			Log.e(TAG,"XML-RPC server returned non-valid return code: "+postID);
			return false;
		}
	}
	
	private void showFetchStatus() {
		fetchProgress.dismiss();
		if(fetchStatus == 5) {
			AlertUtil.showAlert(parentRef,"Status","Blogs fetched OK!");
			// disable the editing of username and password after successful fetch
			EditText username = (EditText)parentRef.findViewById(R.id.usernameconfig);
			EditText password = (EditText)parentRef.findViewById(R.id.passwordconfig);
			username.setEnabled(false);
			password.setEnabled(false);
		} else if (fetchStatus == 6) {
			AlertUtil.showAlert(parentRef,"Status", "Can't find any blogs for this user (null answer).");
		} else if (fetchStatus == 7) {
			AlertUtil.showAlert(parentRef,"Status","Can't find any blogs for this user.");
		} else if (fetchStatus == 8) {
			AlertUtil.showAlert(parentRef,"Status","Can't extract blog names from respose.");
		}  else if (fetchStatus == 9) {
			AlertUtil.showAlert(parentRef,"Status","Can't extract blog ids from response.");
		} else if(fetchStatus == 10) {
			AlertUtil.showAlert(parentRef,"Status","Got a bad response from the server. Check connectivity, username and password!");
		} else {
			AlertUtil.showAlert(parentRef,"Status","Fetch of blogs failed! (Code "+fetchStatus+")");
		}
	}
	
	private void updateSpinner(Spinner list) { 
       	if(blogNames != null) {
       		list.setVisibility(View.VISIBLE);
       		listData = new String[blogNames.length];
       		for(int i = 0; i < blogNames.length; i++) {
       			listData[i] = blogNames[i];
       		}
       		Object res = new ArrayAdapter(parentRef,android.R.layout.simple_spinner_item,listData);
       		ArrayAdapter<CharSequence> adapter = (ArrayAdapter<CharSequence>)res;
       		adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
       		list.setAdapter(adapter);
       		list.setOnItemSelectedListener(this);
       	} else {
       		this.currentySelectedBlog = -1;
       		list.setVisibility(View.INVISIBLE);
       	}
    }

	public String getPostUrl(String authToken) {
		return "http://www.livejournal.com/interface/blogger/";
	}	
	
	public CharSequence getConfigEditorData() {
		// here, we return the id of the selected blog config
		return blogIds[currentySelectedBlog];
	}
	
	
}
